package fragment;

import java.util.HashMap;
import java.util.Hashtable;

import main.Main;

public class ProcessHandler extends QuasiComponentProcessHandler{
	
	private static long processKey = 1;
	
	public static synchronized long getNewProcessKey(){
		return processKey++;
	}
	
	private static long concurrentProcessKey=-1;
	
	private static long getNewConcurentProcessKey(){
		return concurrentProcessKey--;
	}
	
	private static HashMap<String,Long> concurrentsKey= new HashMap<String, Long>();
	
	public static synchronized long getConcurrentProcessKey(String id){
		if(!concurrentsKey.containsKey(id)) concurrentsKey.put(id, getNewConcurentProcessKey());
		return concurrentsKey.get(id);
	}
	
	Object lock=new Object();
	ProcessMsgHandler pm=new ProcessMsgHandler();
	
	@Override
	public long getProcess() {
		return pm.getProcess();
	}


	@Override
	public void updateProcess() {
		synchronized(lock){
			if(this.architecture().getGenerationCapabilities().contains(Main.finalElement))
				updateProcessFinal();
			else
				updateProcessNonFinal();	
		}	
	}
	
	private void updateProcessNonFinal(){
		if (architecture().getGenerationCapabilities().isEmpty())updateProcessSpecial();
		else{
			if(this.architecture().getState().equals(AgentState.SATISFIED)){
		
			Hashtable<String, Integer> stim=this.architecture().getStimulators();
			long process=pm.getProcess();
		
			Hashtable<Long, Integer> stimByProcess=new Hashtable<Long, Integer>();
		
			//order process by received stimulations
			for(String id : stim.keySet()){
				long copyProcess=architecture().getFragment(id).currentProcess();
				if(copyProcess!=0 
						&& ! architecture().getFragment(id).getGenerationCapabilities().isEmpty()){	//do not take special fragments in account
					if(!stimByProcess.contains(copyProcess)) stimByProcess.put(copyProcess,0); //if new process add it
					//a new fragment contribute to a process
					stimByProcess.put(copyProcess,stimByProcess.get(copyProcess)+stim.get(id));
				}
			}
		
			//update the process by choosing the one that has the most stimulation (or possibly no process at all : 0)
		
			if( stimByProcess.keySet().isEmpty()){
				process=0;	//if no one stimulate, the fragment belong to no process
			}
			else{
			
				//initialisation with the first element
				int stimMax=stimByProcess.elements().nextElement();
				process=stimByProcess.keys().nextElement();
				
				for(Long k : stimByProcess.keySet()){
					if( stimByProcess.get(k) > stimMax || (k>0 && process<0)) {
						stimMax = stimByProcess.get(k);
						process=k;
					}
				}
				
				//Search for concurrent process	
				for(String s : this.architecture().getGenerationCapabilities())
					for(Fragment f : this.architecture().findAllPotentialGenerators(s))
						if( f.getId()!= this.architecture().getId() 
								&& f.currentProcess() == process 
								&& f.getCurrentStimulation()>= this.architecture().getCurrentStimulation()
								&& !f.getGenerationCapabilities().isEmpty()) { //do not take special fragments in account
							process=ProcessHandler.getConcurrentProcessKey(this.architecture().getId());
						}
			}
		
			pm.setProcess(process);
		
		}
		else
			pm.setProcess(0);
	}
	}

	private void updateProcessFinal() {		//this is for terminal fragment
				
		long process=pm.getProcess();
		
		if(this.architecture().getState().equals(AgentState.SATISFIED)){
			if(process == 0){
				pm.setProcess(ProcessHandler.getNewProcessKey());
			}
		}
		else{
			pm.setProcess(0);
		}
		
	}
	
	private void updateProcessSpecial(){
		
		int max=Integer.MIN_VALUE;
		long process=0l;
		
		for( String e : architecture().requiredElements()){
			for(Fragment f : this.architecture().findAllPotentialGenerators(e)){
				if(f.getCurrentStimulation()>max) {
					max=f.getCurrentStimulation(); 
					process=f.currentProcess();
				}
			}
		}
		this.pm.setProcess(process);
	}
	
	
	@Override
	public void start() {
		
	}

	private class ProcessMsgHandler{			
		private Object lock=new Object();
		
		private long process= 0;
		
		public long getProcess(){
			synchronized (lock){
				return process;
			}
		}
		
		public void setProcess(long val){
			synchronized (lock){
				process=val;
			}
		}
	}
		
}
